<?php
namespace Tbf\Bundle\AlipayBundle\Api;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\EntityManager;
use Guzzle\Http\Url;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Tbf\Bundle\AlipayBundle\Config;
use Tbf\Bundle\AlipayBundle\Entity\Transaction;
use Tbf\Bundle\AlipayBundle\Model\AlipaySign;
use Tbf\Bundle\AlipayBundle\Model\CreatePaymentRequest;
use Tbf\Bundle\AlipayBundle\Model\CreatePaymentResponse;
use Tbf\Bundle\CallbackSerializerBundle\CallbackSerializer;
use Tbf\Component\Crypto\CryptoRandUtil;
use Tbf\Component\Exception\ExceptionFunction;

/**
 * 创建交易api
 */
abstract class AbstractCreatePayment {
    /**
     * 创建请求数据
     * @param CreatePaymentRequest $request
     * @return array map[string]string
     */
    abstract protected function buildRequestData(CreatePaymentRequest $request);

    /**
     * 这个创建交易的类型
     * @return string
     */
    abstract protected function getCreatePaymentType();
    /**
     * @var Config
     */
    protected $config;
    /** @var EntityManager */
    protected $em;
    /** @var CallbackSerializer  */
    protected $callback_serializer;
    //当前的日志对象
    /** @var  LoggerInterface */
    protected $logger;

    function __construct(Config $config,
                         EntityManager $em,
                         CallbackSerializer $callback_serializer,
                         LoggerInterface $logger
            ){
        $this->config = $config;
        $this->em = $em;
        $this->callback_serializer = $callback_serializer;
        $this->logger = $logger;
    }
    /**
     * 页面同步返回回调
     * @param Request $request
     * @return Response
     */
    function onPageCallback(Request $request){
        return $this->logWrapper(array($this,'_onPageCallback'),$request);
    }

    /**
     * api异步返回回调
     * @param Request $request
     * @return mixed
     */
    function onApiCallback(Request $request){
        return $this->logWrapper(array($this,'_onApiCallback'),$request);
    }
    /**
     * 发起 请求
     * @param CreatePaymentRequest $request
     * @return array
     * func(request CreatePaymentRequest)(response Response,err error)
     */
    function request(CreatePaymentRequest $request){
        $tradeid = $request->getTradeId();
        if (empty($tradeid)){
            $request->setTradeId(CryptoRandUtil::gen_uuid_v4());
            $this->logger->info('AbstractCreatePayment::request generate trade_id',array(
                'trade_id'=>$request->getTradeId(),
            ));
        }
        $request_data = $this->buildRequestData($request);
        $request_data = array_merge($request_data,$this->config->getSellerAccountRequestArray());
        $request_data = AlipaySign::md5SignArray($request_data,$this->config->getAppSecret());

        $this->logger->debug('AbstractCreatePayment::request request data',array(
            'trade_id'=>$request->getTradeId(),
            'request_data'=>$request_data
        ));

        $request_url = Url::factory($this->config->getAlipayGateway());
        $request_url->setQuery($request_data);

        $response = new RedirectResponse((string)$request_url);

        $this->logger->debug('AbstractCreatePayment::request response body',array(
            'trade_id'=>$request->getTradeId(),
            'body'=>(string)$response
        ));

        $transaction = $this->em->getRepository('\Tbf\Bundle\AlipayBundle\Entity\Transaction')
            ->find($request->getTradeId());
        if (empty($transaction)){
            $transaction = new Transaction();
        }

        if ($transaction->getStatus()===Transaction::STATUS_TRADE_FINISH){
            $this->logger->info('AbstractCreatePayment::request has already pay this trade',array(
                'trade_id'=>$request->getTradeId(),
            ));
            return array(null,'你已经支付过了');
        }
        $transaction->setId($request->getTradeId());
        $transaction->setApiCallback(
            $this->callback_serializer->toString($request->getApiCallback())
        );
        $transaction->setPageCallback(
            $this->callback_serializer->toString($request->getPageCallback())
        );
        $transaction->setMoney($request->getMoney());
        $transaction->setCreatePaymentType($this->getCreatePaymentType());
        $transaction->setSubject($request->getSubject());
        $transaction->setExtraData($request->getExtraData());
        $this->em->persist($transaction);

        $this->em->flush();
        return array($response,null);
    }

   protected function logWrapper($callback,Request $request){
        $this->logger->debug('AbstractCreatePayment::logWrapper start',array(
            'request'=>(string)$request
        ));
        try{
            list($response,$err) = $callback($request);
        }catch(\Exception $e){
            $this->logger->error('AbstractCreatePayment::logWrapper catch exception',array(
                'exception'=>ExceptionFunction::exceptionToArray($e),
            ));
            throw $e;
        }
        return $response;
    }
    protected function _onPageCallback(Request $request){
        $data = $request->query->all();
        $this->logger->debug('AbstractCreatePayment::_onPageCallback start',array(
            'data'=>$data
        ));
        list($ret,$err) = $this->onApiCallbackApi($data);
        if ($err!==null){
            $this->logger->error('AbstractCreatePayment::_onPageCallback catch error',array(
                'error'=>$err
            ));
            //连事务都没有查出来,也就不能调用page的回调了...
            if (empty($ret['transaction'])){
                return array(new Response($err,400),$err);
            }
        }
        /** @var \Tbf\Bundle\AlipayBundle\Entity\Transaction $transaction */
        $transaction = $ret['transaction'];
        $response = $ret['response'];
        $page_callback = $this->callback_serializer->fromString($transaction->getPageCallback());
        $response = $page_callback->invoke($response);
        return array($response,null);
    }

    protected function _onApiCallback(Request $request){
        $data = $request->request->all();

        $this->logger->info('AbstractCreatePayment::_onApiCallback start',array(
            'data'=>$data
        ));
        list($ret,$err) = $this->onApiCallbackApi($data);
        if ($err!==null){
            $this->logger->error('AbstractCreatePayment::_onApiCallback catch error',array(
                'error'=>$err
            ));
            return array(new Response($err,200),$err);
        }
        return array(new Response('success',200),null);
    }

    /**
     * 支付宝实际业务处理
     * 3.并发保证成功api回调最多发生一次
     * @param $input
     * @return array
     */
    protected function onApiCallbackApi($input){
        $verify = AlipaySign::md5SignVerifyArray($input,$this->config->getAppSecret());
        if ($verify!==true){
            $this->logger->error('AbstractCreatePayment::onApiCallbackApi request from alipay verify error');
            return array(array(),'支付宝的请求校验错误');
        }
        $paymentResponse = new CreatePaymentResponse();
        $paymentResponse->setTradeId($input['out_trade_no']);
        $paymentResponse->setAlipayTradeId($input['trade_no']);
        $paymentResponse->setTradeStatus($input['trade_status']);
        $paymentResponse->setMoney($input['total_fee']);

        $output = array(
            'response'=>$paymentResponse
        );
        $this->em->beginTransaction();
        $transaction = $this->em->getRepository('\Tbf\Bundle\AlipayBundle\Entity\Transaction')
            ->find($paymentResponse->getTradeId(),LockMode::PESSIMISTIC_WRITE);
        if (empty($transaction)){
            $this->em->rollback();
            $this->logger->error('AbstractCreatePayment::onApiCallbackApi transaction not exist',array(
                'trade_id'=>$paymentResponse->getTradeId()
            ));
            return array($output,'事务不存在');
        }
        $output['transaction'] = $transaction;
        $paymentResponse->setTransaction($transaction);
        $paymentResponse->setExtraData($transaction->getExtraData());

        if ( $this->isSameStatus($transaction->getStatus(),$paymentResponse->getTradeStatus()) ){
            //保证同一个事务的同一个返回状态,仅调用api_callback一次
            $this->em->commit();
            $this->logger->info('AbstractCreatePayment::onApiCallbackApi already handle this status.',array(
                'trade_id'=>$paymentResponse->getTradeId(),
                'trade_status'=>$paymentResponse->getTradeStatus(),
            ));
            return array($output,null);
        }

        $api_callback = $this->callback_serializer->fromString($transaction->getApiCallback());
        $transaction->setStatus($paymentResponse->getTradeStatus());
        $this->em->flush($transaction);
        $this->em->commit();
        /** @var \Tbf\Bundle\CallbackSerializerBundle\CallbackInterface $api_callback */
        $err = $api_callback->invoke($paymentResponse);
        if ($err!==null){
            $this->logger->error('AbstractCreatePayment::onApiCallbackApi user response an error',array(
                'trade_id'=>$paymentResponse->getTradeId(),
                'error'=>$err,
            ));
            $paymentResponse->setApiCallbackError($err);
            return array($output,$err);
        }
        return array($output,null);
    }
    protected function isSameStatus($status1,$status2){
        if ($status1===$status2){
            return true;
        }
        if (in_array($status1,array(
                Transaction::STATUS_TRADE_FINISH,
                Transaction::STATUS_TRADE_SUCCESS
        ))&&
            in_array($status2,array(
                Transaction::STATUS_TRADE_FINISH,
                Transaction::STATUS_TRADE_SUCCESS
            ))
        ){
            return true;
        }
        return false;
    }

    /**
     * 支付宝receive_name字段长度bug
     * @param $string
     */
    protected function handleReceiveName($origin){
        if (strlen($origin)===1){
            return $origin.',';
        }else{
            return $origin;
        }
    }
}